/*
 * Copyright (c) [2023] Huawei Technologies Co.,Ltd.All rights reserved.
 *
 * OpenArkCompiler is licensed under Mulan PSL v2.
 * You can use this software according to the terms and conditions of the Mulan PSL v2.
 * You may obtain a copy of Mulan PSL v2 at:
 *
 *     http://license.coscl.org.cn/MulanPSL2
 *
 * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR
 * FIT FOR A PARTICULAR PURPOSE.
 * See the Mulan PSL v2 for more details.
 */
#include "icp.h"

// Perform Indirect Call Promotion Here.
namespace maple {
struct IcallAndParent {
  IcallAndParent(IcallNode *i, BlockNode *b) : icall(i), block(b) {}
  IcallNode *icall;
  BlockNode *block;
};

void FindIcalls(StmtNode &stmt, BlockNode *curBlock, std::vector<IcallAndParent> &icalls) {
  switch (stmt.GetOpCode()) {
    case OP_icallprotoassigned:
    case OP_icallproto: {
      for (auto pragmaId : *stmt.GetPragmas()) {
        auto *pragma = GlobalTables::GetGPragmaTable().GetPragmaFromindex(pragmaId);
        if (!pragma || pragma->GetKind() != CPragmaKind::PRAGMA_icall_hint) {
          continue;
        }
        (void)icalls.emplace_back(static_cast<IcallNode*>(&stmt), curBlock);
        break;
      }
      break;
    }
    case OP_if: {
      auto &ifStmtNode = static_cast<IfStmtNode&>(stmt);
      if (ifStmtNode.GetThenPart() != nullptr) {
        FindIcalls(*ifStmtNode.GetThenPart(), curBlock, icalls);
      }
      if (ifStmtNode.GetElsePart() != nullptr) {
        FindIcalls(*ifStmtNode.GetElsePart(), curBlock, icalls);
      }
      break;
    }
    case OP_while:
    case OP_dowhile: {
      auto &whileStmtNode = static_cast<WhileStmtNode&>(stmt);
      if (whileStmtNode.GetBody() != nullptr) {
        FindIcalls(*whileStmtNode.GetBody(), curBlock, icalls);
      }
      break;
    }
    case OP_block: {
      auto &block = static_cast<BlockNode&>(stmt);
      StmtNode *next = nullptr;
      for (StmtNode *stmtNode = block.GetFirst(); stmtNode != nullptr; stmtNode = next) {
        FindIcalls(*stmtNode, &block, icalls);
        next = stmtNode->GetNext();
      }
      break;
    }
    default: {
      break;
    }
  }
}

void PerformIcallHint(MIRFunction &caller, MIRBuilder &mirBuilder) {
  mirBuilder.SetCurrentFunction(caller);
  std::vector<IcallAndParent> icalls;
  FindIcalls(*caller.GetBody(), nullptr, icalls);
  auto *preferInline = PreferInlinePragma::CreatePIP(
      static_cast<uint32>(GlobalTables::GetGPragmaTable().GetPragmaTable().size()), "ON");
  if (preferInline->GetIndex() == GlobalTables::GetGPragmaTable().GetPragmaTable().size()) {
    (void)GlobalTables::GetGPragmaTable().GetPragmaTable().emplace_back(preferInline);
  }
  // process nolimited icall_hint pragma
  for (auto &icallPair : icalls) {
    if (!icallPair.block) {
      continue;
    }
    auto *icall = icallPair.icall;
    for (auto pragmaId : *icall->GetPragmas()) {
      auto *pragma = GlobalTables::GetGPragmaTable().GetPragmaFromindex(pragmaId);
      if (!pragma || pragma->GetKind() != CPragmaKind::PRAGMA_icall_hint) {
        continue;
      }
      auto *icallHint = static_cast<IcallHintPragma*>(pragma);
      auto *callee = mirBuilder.GetFunctionFromName(icallHint->GetFuncName());
      if (!callee || callee->GetBody() == nullptr) {  // can not see the func body, skip it
        continue;
      }
      auto calleeID = callee->GetPuidx();
      // replace old icall by new dcall with runtime guard
      // 1. create builtin expect ` __builtin_expect(eq (funcptr, addroffunc $callee)) `
      auto *addroffunc = mirBuilder.CreateExprAddroffunc(calleeID, caller.GetCodeMemPool());
      auto *funcptr = icall->Opnd(0);
      MapleVector<BaseNode *> ops {caller.GetCodeMemPoolAllocator().Adapter()};
      BaseNode *cond = mirBuilder.CreateExprCompare(OP_eq, *GlobalTables::GetTypeTable().GetUInt1(),
          *GlobalTables::GetTypeTable().GetAddr64(), funcptr, addroffunc);
      ops.push_back(cond);
      ops.push_back(mirBuilder.CreateIntConst(1, PTY_i64));
      cond = mirBuilder.CreateExprIntrinsicop(
          INTRN_C___builtin_expect, OP_intrinsicop, *GlobalTables::GetTypeTable().GetInt64(), ops);
      // 2. create if guard
      cond = mirBuilder.CreateExprCompare(OP_ne, *GlobalTables::GetTypeTable().GetUInt1(),
          *GlobalTables::GetTypeTable().GetInt64(), cond, mirBuilder.CreateIntConst(0, PTY_i64));
      auto *ifnode = mirBuilder.CreateStmtIfThenElse(cond);
      // 3. generate new func call
      MapleVector<BaseNode *> args {caller.GetCodeMemPoolAllocator().Adapter()};
      for (size_t i = 1; i < icall->NumOpnds(); i++) {
        args.push_back(icall->Opnd(i)->CloneTree(caller.GetCodeMemPoolAllocator()));
      }
      if (icall->GetOpCode() == OP_icallproto) {
        auto *call = mirBuilder.CreateStmtCall(calleeID, args);
        (void)call->GetPragmas()->emplace(preferInline->GetIndex());
        ifnode->GetThenPart()->AddStatement(call);
      } else {
        auto *call = mirBuilder.CreateStmtCallAssigned(calleeID, args, icall->GetCallReturnSymbol(*caller.GetModule()));
        (void)call->GetPragmas()->emplace(preferInline->GetIndex());
        ifnode->GetThenPart()->AddStatement(call);
      }
      // 4. update code
      icallPair.block->ReplaceStmt1WithStmt2(icall, ifnode);
      ifnode->GetElsePart()->AddStatement(icall);
      icallPair.block = ifnode->GetElsePart();
    }
  }
}

void DoDevirtual(MIRModule &m) {
  MIRBuilder dexMirBuilder(&m);
  for (uint32 i = 0; i < GlobalTables::GetFunctionTable().GetFuncTable().size(); ++i) {
    MIRFunction *func = GlobalTables::GetFunctionTable().GetFunctionFromPuidx(i);
    if (func == nullptr || func->IsEmpty()) {
      continue;
    }
    if (!func->HasDevirtualPragma()) {
      continue;
    }
    PerformIcallHint(*func, dexMirBuilder);
  }
}

bool M2MICP::PhaseRun(maple::MIRModule &m) {
  DoDevirtual(m);
  return true;
}

void M2MICP::GetAnalysisDependence(AnalysisDep &aDep) const {
  aDep.PreservedAllExcept<M2MCallGraph>();
}
}  // namespace maple
